package comparation;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;

import dataAccess.databaseManagement.entity.AssetEntity;
import dataAccess.databaseManagement.entity.PriceEntity;

public class MatchingAlgorithm2b {
	private int intervalPrice = 5;
	private int intervalVolume = 5;
	private double priceCoefficent = 0.4;
	private double volumeCoefficent = 0.6;
	private int noSample = 5;
	
	private HashMap<AssetEntity, ArrayList<PriceEntity>> references;
	private ArrayList<PriceEntity> priceEntities;
	private ArrayList<Double> parameterList = new ArrayList<Double>();
	
	public MatchingAlgorithm2b() {
	}
	
	public MatchingAlgorithm2b(int intervalPrice, double priceCoefficent, double volumeCoefficent, int noSample,HashMap<AssetEntity, ArrayList<PriceEntity>> references, ArrayList<PriceEntity> priceEntities) {
		this.intervalPrice = intervalPrice;
		this.priceCoefficent = priceCoefficent;
		this.volumeCoefficent = volumeCoefficent;
		this.noSample = noSample;
		this.references = references;
		this.priceEntities = priceEntities;
	}
	
	public double predict(int i) {
		ArrayList<PriceEntity> matchingPriceObject = new ArrayList<PriceEntity>();
		for (int j = i - intervalPrice; j < i; ++j) {
			matchingPriceObject.add(priceEntities.get(j));
		}
		
		ArrayList<PriceEntity> matchingVolumeObject = new ArrayList<PriceEntity>();
		for (int j = i - intervalVolume; j < i; ++j) {
			matchingVolumeObject.add(priceEntities.get(j));
		}		
		
		ArrayList<Double> volume = normalize(getVolume(matchingVolumeObject));
		ArrayList<Double> closePrices = normalize(getClosePrices(matchingPriceObject));
		
		ArrayList<ArrayList<PriceEntity>> theBestMatchings = new ArrayList<ArrayList<PriceEntity>>();
		ArrayList<PriceEntity> theBestPredicts = new ArrayList<PriceEntity>();
		ArrayList<Double> minDistances = new ArrayList<Double>();

		for (int j = 0; j < noSample; ++j) {
			minDistances.add(Double.MAX_VALUE);
			theBestMatchings.add(new ArrayList<PriceEntity>());
			theBestPredicts.add(new PriceEntity());
		}		
				
		for (int j = i-1; j >= Math.max(intervalPrice, intervalVolume); --j) {
			ArrayList<PriceEntity> matchingPriceResult = new ArrayList<PriceEntity>();
			for (int k = j-intervalPrice; k < j; ++k) {
				matchingPriceResult.add(priceEntities.get(k));
			}
			ArrayList<PriceEntity> matchingVolumeResult = new ArrayList<PriceEntity>();
			for (int k = j-intervalVolume; k < j; ++k) {
				matchingVolumeResult.add(priceEntities.get(k));
			}
			
			//matching
			{
				ArrayList<Double> closePricesMatching = normalize(getClosePrices(matchingPriceResult));
				double priceDistance = euclideanDistance(closePrices,closePricesMatching);
				ArrayList<Double> volumeMatching = normalize(getVolume(matchingVolumeResult));
				double volumeDistance = euclideanDistance(volume,volumeMatching);
				double distance = priceCoefficent*priceDistance + volumeCoefficent*volumeDistance;
				
				int insert;
				for (insert = 0; insert < noSample && distance > minDistances.get(insert); ++insert)
					;

				if (insert < noSample) {
					for (int k = noSample - 1; k > insert; --k) {
						minDistances.set(k, minDistances.get(k - 1));
						theBestMatchings.set(k,theBestMatchings.get(k - 1));
						theBestPredicts.set(k,theBestPredicts.get(k - 1));
					}

					minDistances.set(insert, distance);
					if (intervalPrice > intervalVolume) {
						theBestMatchings.set(insert, matchingPriceResult);
					} else {
						theBestMatchings.set(insert, matchingVolumeResult);
					}
					theBestPredicts.set(insert,priceEntities.get(j));
				}
			}

			
		}
		
		//references asset
		for (AssetEntity asset : references.keySet()) {
			ArrayList<PriceEntity> entityList = references.get(asset);
						
			for (int j = findIndex(priceEntities.get(i).getDate(), entityList)-1; j >= Math.max(intervalPrice, intervalVolume); --j) {
				ArrayList<PriceEntity> matchingPriceResult = new ArrayList<PriceEntity>();
				for (int k = j-intervalPrice; k < j; ++k) {
					matchingPriceResult.add(entityList.get(k));
				}
				ArrayList<PriceEntity> matchingVolumeResult = new ArrayList<PriceEntity>();
				for (int k = j-intervalVolume; k < j; ++k) {
					matchingVolumeResult.add(entityList.get(k));
				}
				
				//matching
				{
					ArrayList<Double> closePricesMatching = normalize(getClosePrices(matchingPriceResult));
					double priceDistance = euclideanDistance(closePrices,closePricesMatching);
					ArrayList<Double> volumeMatching = normalize(getVolume(matchingVolumeResult));
					double volumeDistance = euclideanDistance(volume,volumeMatching);
					double distance = priceCoefficent*priceDistance + volumeCoefficent*volumeDistance;
					
					int insert;
					for (insert = 0; insert < noSample && distance > minDistances.get(insert); ++insert)
						;

					if (insert < noSample) {
						for (int k = noSample - 1; k > insert; --k) {
							minDistances.set(k, minDistances.get(k - 1));
							theBestMatchings.set(k,theBestMatchings.get(k - 1));
							theBestPredicts.set(k,theBestPredicts.get(k - 1));
						}

						minDistances.set(insert, distance);
						if (intervalPrice > intervalVolume) {
							theBestMatchings.set(insert, matchingPriceResult);
						} else {
							theBestMatchings.set(insert, matchingVolumeResult);
						}
						theBestPredicts.set(insert,entityList.get(j));
					}
				}

								
			}
		}
		
		parameterList.clear();
		
		double total = 0;
		for (int j = 0; j < noSample; ++j) {
			if (theBestMatchings.get(j).size() == 0) {
				break;
			}

			total += 1.0/minDistances.get(j);
			//total++;
		}

		double rateOfChange = 0;
		for (int j = 0; j < noSample; ++j) {
			if (theBestMatchings.get(j).size() == 0) {
				break;
			}

			double rate = theBestPredicts.get(j).getClose()/theBestMatchings.get(j).get(theBestMatchings.get(j).size()-1).getClose() - 1;
			
			rateOfChange += rate*(1.0/minDistances.get(j))/total;
			//rateOfChange += (1.0/total)*rate;
			
			parameterList.add(rate);
			parameterList.add((1.0/minDistances.get(j))/total);
			//parameterList.add(minDistances.get(j));
			for (int k = 0; k < theBestMatchings.get(j).size(); ++k) {
				parameterList.add(theBestMatchings.get(j).get(k).getClose());
				parameterList.add(theBestMatchings.get(j).get(k).getVolume());
			}
			parameterList.add(theBestPredicts.get(j).getClose());

		}

		return (priceEntities.get(i-1).getClose() + priceEntities.get(i-1).getClose()*rateOfChange);
	}
	
	private static int findIndex(Date date,
			ArrayList<PriceEntity> priceEntities) {
		for (int i = 0; i < priceEntities.size(); ++i) {
			if (priceEntities.get(i).getDate().compareTo(date) > 0) {
				return i-1;
			}
		}
		return -1;
	}

	private static ArrayList<Double> getVolume(
			ArrayList<PriceEntity> priceMatching) {
		ArrayList<Double> closePrices = new ArrayList<Double>();
		
		for (int i = 0; i < priceMatching.size(); ++i) {
			closePrices.add(priceMatching.get(i).getVolume());
		}
		
		return closePrices;
	}

	private static ArrayList<Double> getClosePrices(
			ArrayList<PriceEntity> priceList) {
		ArrayList<Double> closePrices = new ArrayList<Double>();
		
		for (int i = 0; i < priceList.size(); ++i) {
			closePrices.add(priceList.get(i).getClose());
		}
		
		return closePrices;
	}

	public static double euclideanDistance(ArrayList<Double> x1, ArrayList<Double> x2) {
		if (x1.size() != x2.size()) {
			return 0;
		}
		
		double result = 10e-5;
		
		for (int i = 0; i < x1.size(); ++i) {
			result += Math.pow(x1.get(i) - x2.get(i),2);
		}
		
		return Math.sqrt(result);
	}
	
	public static double mean (ArrayList<Double> x) {
		double mean = 0;
		for (Double e : x) {
			mean += e;
		}
		mean /= x.size();
		return mean;
	}
	
	public static double std (ArrayList<Double> x) {
		double variance = 10e-5;
		double mean = mean(x);
		for (Double e : x) {
			variance += (e - mean)*(e - mean);
		}
		variance /= x.size();
		return Math.sqrt(variance);
	}
	
	public static ArrayList<Double> normalize(ArrayList<Double> x) {
		double mean = mean(x);
		double std = std(x);
		
		for (int i = 0; i < x.size(); ++i) {
			x.set(i,(x.get(i) - mean)/std);
		}		
		
		return x;
	}

	
	public double getIntervalPrice() {
		return intervalPrice;
	}
	public void setIntervalPrice(int interval) {
		this.intervalPrice = interval;
	}
	public double getPriceCoefficent() {
		return priceCoefficent;
	}
	public void setPriceCoefficent(double priceCoefficent) {
		this.priceCoefficent = priceCoefficent;
	}
	public double getVolumeCoefficent() {
		return volumeCoefficent;
	}
	public void setVolumeCoefficent(double volumeCoefficent) {
		this.volumeCoefficent = volumeCoefficent;
	}
	public HashMap<AssetEntity, ArrayList<PriceEntity>> getReferences() {
		return references;
	}
	public void setReferences(HashMap<AssetEntity, ArrayList<PriceEntity>> references) {
		this.references = references;
	}
	public ArrayList<PriceEntity> getPriceEntities() {
		return priceEntities;
	}
	public void setPriceEntities(ArrayList<PriceEntity> priceEntities) {
		this.priceEntities = priceEntities;
	}
	
	public int getNoSample() {
		return noSample;
	}

	public void setNoSample(int noSamplePrice) {
		this.noSample = noSamplePrice;
	}

	public int getIntervalVolume() {
		return intervalVolume;
	}

	public void setIntervalVolume(int intervalVolume) {
		this.intervalVolume = intervalVolume;
	}

	public ArrayList<Double> getParameterList() {
		return parameterList;
	}

	public void setParameterList(ArrayList<Double> parameterList) {
		this.parameterList = parameterList;
	}
}
